﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel.Design;
using System.Drawing;

namespace System.Windows.Forms.Design.Behavior;

/// <summary>
///  This abstract class represents the Behavior objects that are managed
///  by the BehaviorService.  This class can be extended to develop any
///  type of UI 'behavior'.  Ex: selection, drag, and resize behaviors.
/// </summary>
public abstract class Behavior
{
    private readonly bool _callParentBehavior;
    private readonly BehaviorService? _behaviorService;

    protected Behavior()
    {
    }

    /// <param name="callParentBehavior">
    ///  `true` if the parentBehavior should be called if it exists. The parentBehavior is the next behavior on
    ///  the behaviorService stack.If true, <paramref name="behaviorService"/> must be non-null.
    /// </param>
    protected Behavior(bool callParentBehavior, BehaviorService? behaviorService)
    {
        if ((callParentBehavior) && (behaviorService is null))
        {
            throw new ArgumentException(null, nameof(behaviorService));
        }

        _callParentBehavior = callParentBehavior;
        _behaviorService = behaviorService;
    }

    private Behavior? GetNextBehavior => _behaviorService?.GetNextBehavior(this);

    /// <summary>
    ///  The cursor that should be displayed for this behavior.
    /// </summary>
    public virtual Cursor Cursor => Cursors.Default;

    /// <summary>
    ///  Returning true from here indicates to the BehaviorService that all MenuCommands the designer receives
    ///  should have their state set to 'Enabled = false' when this Behavior is active.
    /// </summary>
    public virtual bool DisableAllCommands
    {
        get
        {
            if (_callParentBehavior && GetNextBehavior is not null)
            {
                return GetNextBehavior.DisableAllCommands;
            }
            else
            {
                return false;
            }
        }
    }

    /// <summary>
    ///  Called from the BehaviorService, this function provides an opportunity for the Behavior to return its
    ///  own custom MenuCommand thereby intercepting this message.
    /// </summary>
    public virtual MenuCommand? FindCommand(CommandID commandId)
    {
        try
        {
            if (_callParentBehavior && GetNextBehavior is not null)
            {
                return GetNextBehavior.FindCommand(commandId);
            }
            else
            {
                return null;
            }
        }
        catch // Catch any exception and return null MenuCommand.
        {
            return null;
        }
    }

    /// <summary>
    ///  The heuristic we will follow when any of these methods are called
    ///  is that we will attempt to pass the message along to the glyph.
    ///  This is a helper method to ensure validity before forwarding the message.
    /// </summary>
    private Behavior? GetGlyphBehavior(Glyph? g)
    {
        return g?.Behavior is not null && g.Behavior != this ? g.Behavior : null;
    }

    /// <summary>
    ///  A behavior can request mouse capture through the behavior service by pushing itself with
    ///  PushCaptureBehavior.  If it does so, it will be notified through OnLoseCapture when capture is lost.
    ///  Generally the behavior pops itself at this time. Capture is lost when one of the following occurs:
    ///
    ///  1. Someone else requests capture.
    ///  2. Another behavior is pushed.
    ///  3. This behavior is popped.
    ///
    ///  In each of these cases OnLoseCapture on the behavior will be called.
    /// </summary>
    public virtual void OnLoseCapture(Glyph? g, EventArgs e)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            GetNextBehavior.OnLoseCapture(g, e);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            behavior.OnLoseCapture(g, e);
        }
    }

    /// <summary>
    ///  When any MouseDown message enters the BehaviorService's AdornerWindow (nclbuttondown, lbuttondown,
    ///  rbuttondown, nclrbuttondown) it is first passed here, to the top-most Behavior in the BehaviorStack.
    ///  Returning 'true' from this function signifies that the Message was 'handled' by the Behavior and should
    ///  not continue to be processed.
    /// </summary>
    public virtual bool OnMouseDoubleClick(Glyph? g, MouseButtons button, Point mouseLoc)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            return GetNextBehavior.OnMouseDoubleClick(g, button, mouseLoc);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            return behavior.OnMouseDoubleClick(g, button, mouseLoc);
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    ///  When any MouseDown message enters the BehaviorService's AdornerWindow (nclbuttondown, lbuttondown,
    ///  rbuttondown, nclrbuttondown) it is first passed here, to the top-most Behavior in the BehaviorStack.
    ///  Returning 'true' from this function signifies that the Message was 'handled' by the Behavior and
    ///  should not continue to be processed.
    /// </summary>
    public virtual bool OnMouseDown(Glyph? g, MouseButtons button, Point mouseLoc)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            return GetNextBehavior.OnMouseDown(g, button, mouseLoc);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            return behavior.OnMouseDown(g, button, mouseLoc);
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    ///  When the mouse pointer's location is positively hit-tested with a different Glyph than previous
    ///  hit-tests, this event is fired on the Behavior associated with the Glyph.
    /// </summary>
    public virtual bool OnMouseEnter(Glyph? g)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            return GetNextBehavior.OnMouseEnter(g);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            return behavior.OnMouseEnter(g);
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    ///  When a MouseHover message enters the BehaviorService's AdornerWindow it is first passed here, to the
    ///  top-most Behavior in the BehaviorStack.  Returning 'true' from this function signifies that the Message
    ///  was 'handled' by the Behavior and should not continue to be processed.
    /// </summary>
    public virtual bool OnMouseHover(Glyph? g, Point mouseLoc)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            return GetNextBehavior.OnMouseHover(g, mouseLoc);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            return behavior.OnMouseHover(g, mouseLoc);
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    ///  When the mouse pointer leaves a positively hit-tested Glyph with a valid Behavior, this method is invoked.
    /// </summary>
    public virtual bool OnMouseLeave(Glyph? g)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            return GetNextBehavior.OnMouseLeave(g);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            return behavior.OnMouseLeave(g);
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    ///  When any MouseMove message enters the BehaviorService's AdornerWindow (mousemove, ncmousemove) it is
    ///  first passed here, to the top-most Behavior in the BehaviorStack.  Returning 'true' from this method
    ///  signifies that the Message was 'handled' by the Behavior and should not continue to be processed.
    /// </summary>
    public virtual bool OnMouseMove(Glyph? g, MouseButtons button, Point mouseLoc)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            return GetNextBehavior.OnMouseMove(g, button, mouseLoc);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            return behavior.OnMouseMove(g, button, mouseLoc);
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    ///  When any MouseUp message enters the BehaviorService's AdornerWindow
    ///  (nclbuttonupown, lbuttonup, rbuttonup, nclrbuttonup) it is first
    ///  passed here, to the top-most Behavior in the BehaviorStack.  Returning
    ///  'true' from this function signifies that the Message was 'handled' by
    ///  the Behavior and should not continue to be processed.
    /// </summary>
    public virtual bool OnMouseUp(Glyph? g, MouseButtons button)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            return GetNextBehavior.OnMouseUp(g, button);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            return behavior.OnMouseUp(g, button);
        }
        else
        {
            return false;
        }
    }

    // OLE DragDrop virtual methods

    /// <summary>
    ///  OnDragDrop can be overridden so that a Behavior can specify its own Drag/Drop rules.
    /// </summary>
    public virtual void OnDragDrop(Glyph? g, DragEventArgs e)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            GetNextBehavior.OnDragDrop(g, e);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            behavior.OnDragDrop(g, e);
        }
    }

    /// <summary>
    ///  OnDragEnter can be overridden so that a Behavior can specify its own Drag/Drop rules.
    /// </summary>
    public virtual void OnDragEnter(Glyph? g, DragEventArgs e)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            GetNextBehavior.OnDragEnter(g, e);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            behavior.OnDragEnter(g, e);
        }
    }

    /// <summary>
    ///  OnDragLeave can be overridden so that a Behavior can specify its own Drag/Drop rules.
    /// </summary>
    public virtual void OnDragLeave(Glyph? g, EventArgs e)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            GetNextBehavior.OnDragLeave(g, e);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            behavior.OnDragLeave(g, e);
        }
    }

    /// <summary>
    ///  OnDragOver can be overridden so that a Behavior can specify its own Drag/Drop rules.
    /// </summary>
    public virtual void OnDragOver(Glyph? g, DragEventArgs e)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            GetNextBehavior.OnDragOver(g, e);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            behavior.OnDragOver(g, e);
        }
        else if (e.Effect != DragDropEffects.None)
        {
            e.Effect = (Control.ModifierKeys == Keys.Control) ? DragDropEffects.Copy : DragDropEffects.Move;
        }
    }

    /// <summary>
    ///  OnGiveFeedback can be overridden so that a Behavior can specify its own Drag/Drop rules.
    /// </summary>
    public virtual void OnGiveFeedback(Glyph? g, GiveFeedbackEventArgs e)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            GetNextBehavior.OnGiveFeedback(g, e);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            behavior.OnGiveFeedback(g, e);
        }
    }

    /// <summary>
    ///  QueryContinueDrag can be overridden so that a Behavior can specify its own Drag/Drop rules.
    /// </summary>
    public virtual void OnQueryContinueDrag(Glyph? g, QueryContinueDragEventArgs e)
    {
        if (_callParentBehavior && GetNextBehavior is not null)
        {
            GetNextBehavior.OnQueryContinueDrag(g, e);
        }
        else if (GetGlyphBehavior(g) is Behavior behavior)
        {
            behavior.OnQueryContinueDrag(g, e);
        }
    }
}
